package com.duanzz.sqlSession;

import com.duanzz.config.BoundSql;
import com.duanzz.pojo.Configuration;
import com.duanzz.pojo.MappedStatement;
import com.duanzz.utils.GenericTokenParser;
import com.duanzz.utils.ParameterMapping;
import com.duanzz.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {

    /**
     * 线程本地连接
     */
    public static ThreadLocal<Connection> CONNECTION_THREADLOCAL = new ThreadLocal<>();

    /**
     * 返回
     * @return
     */
    @Override
    public ThreadLocal<Connection> getThreadLocal() {
        return CONNECTION_THREADLOCAL;
    }

    /**
     * 查询方法
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     * @throws Exception
     */
    @Override
    public <E> List<E> doQuery(Configuration configuration, MappedStatement mappedStatement,
                               Object... params) throws Exception {
        //执行的jdbc代码

        //1.注册驱动，获取连接
        Connection conn = CONNECTION_THREADLOCAL.get();
        if(conn == null){
            conn = configuration.getDataSource().getConnection();
            conn.setAutoCommit(false);
            CONNECTION_THREADLOCAL.set(conn);
        }

        //2.获取sql语句:select * from user where id = #{id} and username = #{username}
        //转换sql 语句 ： select * from user where id = ? and username = ?
        //转换的过程中，还需要对#{}里的值进行解析存储
        BoundSql boundSql = getBoundSql(mappedStatement.getSql());

        //3.获取预处理对象
        PreparedStatement preparedStatement = conn.prepareStatement(boundSql.getSql());

        //4.设置参数
        //获取入参全路径
        Class<?> parameterTypeClass = getClassType(mappedStatement.getParameterType());

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            //#{}里面的值
            String content = parameterMapping.getContent();
            /* 反射
               全路径
             */
            //如果不是包装类或者基本类型，则按照基本对象处理
            Object obj = null;
            if(Number.class.isAssignableFrom(parameterTypeClass)){
                //包装类子类，设置一个参数
                obj = params[0];
            }else{
                //简单java类
                Field declaredField = parameterTypeClass.getDeclaredField(content);
                //暴力访问
                declaredField.setAccessible(true);
                obj = declaredField.get(params[0]);
            }
            //顺序是相对的
            preparedStatement.setObject(i + 1, obj);

        }

        //5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();

        //6.封装返回结果集合
        //获取resultType
        Class<?> resultTypeClass = getClassType(mappedStatement.getResultType());
        ArrayList<Object> objects = new ArrayList<>();
        while (resultSet.next()) {
            Object obj = resultTypeClass.newInstance();
            //取出源数据
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                //获取字段名
                String columnName = resultSetMetaData.getColumnName(i);
                //获取字段值
                Object value = resultSet.getObject(columnName);
                //使用反射或者内省，根据数据库与实体对应关系
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,
                        resultTypeClass);
                Method method = propertyDescriptor.getWriteMethod();
                method.invoke(obj, value);
            }
            objects.add(obj);
        }
        return (List<E>) objects;
    }

    /**
     * 更新方法
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     */
    @Override
    public int doUpdate(Configuration configuration, MappedStatement mappedStatement,
                        Object... params) throws Exception {
        //获得连接
        Connection conn = CONNECTION_THREADLOCAL.get();
        if(conn == null){
            conn = configuration.getDataSource().getConnection();
            conn.setAutoCommit(false);
            CONNECTION_THREADLOCAL.set(conn);
        }
        //获得带?的sql，和入参参数列表
        BoundSql boundSql = getBoundSql(mappedStatement.getSql());
        //获得入参类型
        Class<?> parameterClassType = getClassType(mappedStatement.getParameterType());
        //创建sql预处理对象
        PreparedStatement pst = conn.prepareStatement(boundSql.getSql());
        //获得入参参名称列表
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            //得到参数的名称
            String content = parameterMappingList.get(i).getContent();
            Object obj = null;
            //如果是包装类
            if(Number.class.isAssignableFrom(parameterClassType)){
                obj = params[0];
            }else{
                //反射给属性设置名称
                Field field = parameterClassType.getDeclaredField(content);
                field.setAccessible(true);
                obj = field.get(params[0]);
            }
            pst.setObject(i + 1, obj);
        }
        return pst.executeUpdate();
    }


    /**
     * 完成的任务
     *
     * 1.解析 #{} 使用？进行代替
     * 2.解析 #{} 里面的值，进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理器：配合标记解析器对 占位符#{} ? 我解析处理
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser =
                new GenericTokenParser("#{", "}", tokenHandler);
        //解析出来的sql 是带 ?的
        String parseSql = genericTokenParser.parse(sql);
        //获取值
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;

    }

    /**
     * 通过全限定类目返回Class类对象
     *
     * @param paramType
     * @return
     * @throws ClassNotFoundException
     */
    public Class<?> getClassType(String paramType) throws ClassNotFoundException {
        if (paramType != null) {
            Class<?> aClass = Class.forName(paramType);
            return aClass;
        } else {
            return null;
        }
    }
}
